Moje odkazy
Obsah článku:
vydáno: 4. 10. 2015 12:25, aktualizováno: 26. 8. 2018 14:13
V pátek jsem narazil na chybu v programu inotifywait
a dneska jsem ji ze zvědavosti trochu prozkoumal. Program za určitých okolností padal (SIGSEGV, core dump). Jedná se o celkem banální chybu, kterých je všude plno, a rád bych na ní ukázal, jak v takovém případě můžeme postupovat.
Inotify je subsystém Jádra (Linuxu), který umožňuje aplikacím reagovat na události v souborovém systému – aplikace od jádra dostává události např. o tom, že byl nějaký soubor vytvořen, smazán, otevřen nebo upraven, a může na ně reagovat. Díky tomu nás třeba textový editor upozorní, že došlo ke změně souboru, který právě editujeme (např. jsme otevřeli tentýž soubor ve dvou editorech a upravujeme ho paralelně). Nebo můžeme po uložení .sql
souboru spustit databázový dotaz nebo třeba po uložení .xhtml
aktualizovat stránku v prohlížeči.
Inotify-tools je balíček obsahující programy inotifywait
a inotifywatch
, které používají subsystém inotify a umožňují nám reagovat na události třeba v bashových skritech nebo prostě jen sledovat, co se na souborovém systému děje (k tomu nemusíme nic programovat, prostě jen spustíme jeden z těchto příkazů).
Příklad: budeme sledovat adresář test
:
$ inotifywait -m -r test/ Setting up watches. Beware: since -r was given, this may take a while! Watches established. test/ MODIFY soubor.txt test/ OPEN soubor.txt test/ MODIFY soubor.txt test/ CLOSE_WRITE,CLOSE soubor.txt
Události se nám vypsaly poté, co jsme v druhém terminálu spustili:
echo ahoj > test/soubor.txt
Díky inotifywait
tedy krásně vidíme, co se na souborovém systému stalo – došlo k vytvoření souboru, jeho otevření, úpravě a zavření se zápisem.
Příkaz inotifywait
má volbu --csv
, která zajistí výstup ve formátu CSV (hodnoty oddělené čárkou). Chyba se projevuje jen při jejím použití – jde tedy o chybu někde ve formátovači CSV výstupu. A přišel jsem na ni díky své škodolibosti: jestliže CSV obsahuje hodnoty oddělené čárkou, co se asi stane, když název souboru nebo adresáře bude obsahovat čárku?
Pokud čárku obsahuje název souboru:
echo ahoj > test/sou,bor.txt
nestane se nic, resp. název se správně obalí uvozovkami:
$ inotifywait --csv -m -r test/ Setting up watches. Beware: since -r was given, this may take a while! Watches established. test/,CREATE,"sou,bor.txt" test/,OPEN,"sou,bor.txt" test/,MODIFY,"sou,bor.txt" test/,"CLOSE_WRITE,CLOSE","sou,bor.txt"
Pokud ale čárku obsahuje název adresáře,
$ mkdir test/adre,sář $ echo ahoj > test/adre,sář/soubor.txt
program spadne:
$ inotifywait --csv -m -r test/ Setting up watches. Beware: since -r was given, this may take a while! Watches established. test/,"CREATE,ISDIR","adre,sář" test/,"OPEN,ISDIR","adre,sář" test/,"ACCESS,ISDIR","adre,sář" test/,"CLOSE_NOWRITE,CLOSE,ISDIR","adre,sář" Neoprávněný přístup do paměti (SIGSEGV) (core dumped [obraz paměti uložen])
a spadne až po vykonání příkazu echo
, ne po mkdir
– tzn. spadne při vytváření souboru, který se nachází v adresáři, jehož název obsahuje čárku.
Protože inotify-tools je svobodný software, nemusíme na neposlušný počítač jen smutně koukat nebo mu nadávat a můžeme s tím něco dělat – pojďme tedy na to – podíváme se, jak program uvnitř funguje, a pokusíme se ho opravit.
Nejprve si vytvoříme oddělené prostředí, ve kterém budeme tento problém řešit – jde jednak o bezpečnost (budeme stahovat cosi z Internetu a následně to kompilovat a spouštět) a jednak o izolaci aplikací – nechceme, aby nám jedna aplikace ovlivňovala chod jiných aplikací.
GNU/Linux nám k tomu poskytuje různé možnosti – od plné virtualizace (KVM, Xen, VirtualBox), přes kontejnerovou (LXC, OpenVZ), různé bezpečnostní moduly (AppArmor, SELinux) po unixové základy, jako jsou uživatelské účty a souborová oprávnění.
V tomto případě si vystačíme s jednoduchým řešením: založíme si druhý uživatelský účet pojmenovaný třeba hacker
, pod kterým budeme pracovat (tento uživatel nemůže číst ani zapisovat soubory našeho hlavního uživatele).
# adduser hacker
Dále budeme potřebovat balíčky jako build-essentials
, make
, gcc
, gdb
, autoconf
, automake
nebo libtool
. Obecně platí, že pokud se na něčem zaseknete, pravděpodobně je to tím, že vám některý z těchto nástrojů chybí – stačí si přečíst chybové hlášky a doinstalovat ho.
Zdrojové kódy budeme v něčem upravovat, potřebujeme tedy editor (např. Emacs, VIM, mcedit nebo jEdit) nebo nějaké IDE. Osobně dávám přednost IDE kvůli lepšímu ladění a pohodlnější práci. Doporučuji Netbeans, což je sice IDE primárně pro Javu, ale má velice dobrou podporu i pro C/C++ a integraci s GDB (GNU Debugger), takže ladění C/C++ programů je v něm skoro tak pohodlné, jako ladění programů v Javě.
Netbeans už instalujeme pod uživatelem hacker
.
Z balíčkovacího systému (v mém případě: aptitude show inotify-tools
) zjistíme domovskou stránku programu a následně cestu k úložišti zdrojáků, odkud si je stáhneme:
$ git clone https://gitlab.com/src-backup.globalcode.info/inotify-tools.git
Není od věci si přečíst soubory README
a INSTALL
. Nebo se můžeme řídit intuicí a zkušenostmi. Vidíme soubor autogen.sh
, tak ho spustíme a on nám vygeneruje soubor configure
, spustíme i ten a máme makefile a spustíme make
:
$ ./autogen.sh $ ./configure $ make
Příkazy toho vypisují poměrně dost, je dobré číst aspoň konce těch výpisů a kontrolovat, jestli se na nich nevyskytuje chyba (typicky nějaká chybějící závislost – viz výše).
Po úspěšné kompilaci nám vznikl soubor src/inotifywait
. Ten spustíme – teď už spouštíme námi zkompilovaný program, ne ten z distribuce.
Mimochodem, než začneme věci opravovat nebo se v něčem vrtat, vždy je potřeba rozběhat původní verzi – až když se nám to povede, tak teprve upravujeme zdrojáky – jinak jen těžko rozlišíme, jestli jsme nefunkčnost způsobili svým zásahem nebo byl program rozbitý už dříve.
Když máme spustitelný program, zkusíme nasimulovat chybu (viz výše). S překvapením ale zjišťujeme, že k této chybě už nedochází, program nepadá.
Proč s překvapením? Přeskočil jsem hodně důležitou fázi, kterou je rešerše nahlášených chyb – je poměrně vzácné, abychom byli první, kdo na chybu narazil, proto je dobré nejdřív prohledat systémy na správu chyb/požadavků (většina projektů používá Bugzillu, Trac, GitLab, Launchpad nebo něco podobného) a zjistit, zda již chyba nebyla hlášena nebo dokonce opravena v novější verzi.
Na stránkách projektu jsem se totiž dočetl:
inotify-tools 3.14 is the latest version, released on the 7th of March 2010.
což je už dost dávno a ve své distribuci verzi 3.14 mám, a proto jsem rešerši neprovedl.
Možná vysvětlení jsou a) v Debianu/Ubuntu kompilují program jinak a jejich 3.14 se chová jinak než moje 3.14, b) v Debianu/Ubuntu zanesli do programu chybu ve formě vlastního patche, c) nějaký vývoj od roku 2010 přeci jen probíhá.
Stáhl jsem si tedy zdrojový balíček z Ubuntu:
$ apt-get source inotify-tools
a pomocí:
$ kompare inotify-tools-3.14/src/ inotify-tools/src/
jsem našel, čím se mj. liší:
V Netbeans jsem pomocí funkce Team / Show Annotations zjistil historii tohoto řádku:
(stejně tak můžeme použít příkaz git annotate src/inotifywait.c
)
C je tedy správně – vývoj probíhá a chyba již byla nahlášena (#36) i opravena (e049c88). Jen tato změna ještě nebyla z vývojové verze převzata do Debianu/Ubuntu (tam se používá poslední vydaná 3.14).
Problém spočívá v tom, že se escapuje text, který už byl jednou escapován (viz řádek 126). Že je to skutečně příčina chyby, jsem ověřil tak, že jsem řádek:
printf("%s,", csv_escape(filename));
zanesl i do svého programu – a ten začal padat.
Teď by tedy stačilo kontaktovat správce balíčků v distribuci, aby začlenili příslušnou změnu od autorů programu…
Ovšem když se nad tím trochu zamyslíme: proč by sakra program měl padat, když se něco escapuje dvakrát? Maximálně by měl dávat chybný výsledek, ale rozhodně by neměl padat. Chyba bude tedy někde hlouběji a změna e049c88
řeší pouze dílčí problém resp. jinou chybu, díky které se na tu skutečnou přišlo.
Podívejme se tedy na funkci csv_escape()
:
char * csv_escape( char * string ) {
static char csv[MAX_STRLEN+1];
static unsigned int i, ind;
if (string == NULL) {
return NULL;
}
if ( strlen(string) > MAX_STRLEN ) {
return NULL;
}
if ( strlen(string) == 0 ) {
return NULL;
}
// May not need escaping
if ( !strchr(string, '"') && !strchr(string, ',') && !strchr(string, '\n')
&& string[0] != ' ' && string[strlen(string)-1] != ' ' ) {
strcpy( csv, string );
return csv;
}
// OK, so now we _do_ need escaping.
csv[0] = '"';
ind = 1;
for ( i = 0; i < strlen(string); ++i ) {
if ( string[i] == '"' ) {
csv[ind++] = '"';
}
csv[ind++] = string[i];
}
csv[ind++] = '"';
csv[ind] = '\0';
return csv;
}
Už na první pohled je podezřelých několik věcí. Proměnná csv
se alokuje s velikostí MAX_STRLEN+1
a vstup se kontroluje, aby nebyl větší než MAX_STRLEN
, ale při escapování se text prodlouží, takže je jasné, že se do MAX_STRLEN+1
nemusí vejít.
A ty statické lokální proměnné jsou taky divné – asi nějaká céčkařská optimalizace, které jako javista nemůžu rozumět…
V debuggeru (GDB + Netbeans) už po pár průchodech cyklem vidíme, že se děje něco nekalého – rostoucí počet uvozovek:
Přidáme si do kódu ladící výpis:
for ( i = 0; i < strlen(string); ++i ) {
printf("%d / %d / %s / %s\n", i, ind, string, csv); // ladíme
if ( string[i] == '"' ) {
csv[ind++] = '"';
}
csv[ind++] = string[i];
}
a výstup nasměrujeme do souboru:
… 24 / 25 / /home/hacker/temp/inotify/a,b/ / "/home/hacker/temp/inotif 25 / 26 / /home/hacker/temp/inotify/a,b/ / "/home/hacker/temp/inotify 26 / 27 / /home/hacker/temp/inotify/a,b/ / "/home/hacker/temp/inotify/ 27 / 28 / /home/hacker/temp/inotify/a,b/ / "/home/hacker/temp/inotify/a 28 / 29 / /home/hacker/temp/inotify/a,b/ / "/home/hacker/temp/inotify/a, 29 / 30 / /home/hacker/temp/inotify/a,b/ / "/home/hacker/temp/inotify/a,b 0 / 1 / "/home/hacker/temp/inotify/a,b/" / "/home/hacker/temp/inotify/a,b/" 1 / 3 / """ome/hacker/temp/inotify/a,b/" / """ome/hacker/temp/inotify/a,b/" 2 / 5 / """""e/hacker/temp/inotify/a,b/" / """""e/hacker/temp/inotify/a,b/" 3 / 7 / """""""hacker/temp/inotify/a,b/" / """""""hacker/temp/inotify/a,b/" 4 / 9 / """""""""cker/temp/inotify/a,b/" / """""""""cker/temp/inotify/a,b/" 5 / 11 / """""""""""er/temp/inotify/a,b/" / """""""""""er/temp/inotify/a,b/" 6 / 13 / """""""""""""/temp/inotify/a,b/" / """""""""""""/temp/inotify/a,b/" 7 / 15 / """""""""""""""emp/inotify/a,b/" / """""""""""""""emp/inotify/a,b/" 8 / 17 / """""""""""""""""p/inotify/a,b/" / """""""""""""""""p/inotify/a,b/" 9 / 19 / """""""""""""""""""inotify/a,b/" / """""""""""""""""""inotify/a,b/" 10 / 21 / """""""""""""""""""""otify/a,b/" / """""""""""""""""""""otify/a,b/" 11 / 23 / """""""""""""""""""""""ify/a,b/" / """""""""""""""""""""""ify/a,b/" 12 / 25 / """""""""""""""""""""""""y/a,b/" / """""""""""""""""""""""""y/a,b/" 13 / 27 / """""""""""""""""""""""""""a,b/" / """""""""""""""""""""""""""a,b/" 14 / 29 / """""""""""""""""""""""""""""b/" / """""""""""""""""""""""""""""b/" 15 / 31 / """""""""""""""""""""""""""""""" / """""""""""""""""""""""""""""""" 16 / 33 / """"""""""""""""""""""""""""""""" / """"""""""""""""""""""""""""""""" 17 / 35 / """"""""""""""""""""""""""""""""""" / """"""""""""""""""""""""""""""""""" … 3789 / 7608 / """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""… 3790 / 7610 / """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""… 3791 / 7612 / """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""… 3792 / 7614 / """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""… 3793 / 7616 / """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""…
Zde vidíme že první volání funkce proběhlo v pořádku: pro vstup /home/hacker/temp/inotify/a,b/
funkce vrátila "/home/hacker/temp/inotify/a,b/"
.
Zatímco druhé (nežádoucí, ale na tom teď nezáleží) volání funkce donekonečna přidávalo uvozovky, resp. přidávalo by, kdyby program nebyl zastaven operačním systémem kvůli tomu, že se proces pokusil zapsat do paměti někam, kam neměl (SIGSEGV
).
Na tomto příkladě hezky vidíme, že u programů psaných v jazyce C nezáleží na velikosti pole (MAX_STRLEN
je 4096), program tuto hranici vesele překročil, přepisoval si svojí paměť dál mimo dané pole a zarazil ho až operační systém v bodě 7616
.
Celé je to ale nějaké divné, protože for
cyklus by měl iterovat od nuly do strlen(string)
a pak se zastavit, ne pokračovat donekonečna. Leda že by se hodnota strlen(string)
v čase měnila. Upravíme si tedy ladící výpis, abychom věděli víc:
printf("i = %u / ind = %u / strlen(string) = %zu / vstup = %s / výstup = %s\n", i, ind, strlen(string), string, csv);
A vidíme, že délka string
od bodu i = 15
a hodnoty 32 skutečně roste:
… i = 13 / ind = 27 / strlen(string) = 32 / vstup = """""""""""""""""""""""""""a,b/" / výstup = """""""""""""""""""""""""""a,b/" i = 14 / ind = 29 / strlen(string) = 32 / vstup = """""""""""""""""""""""""""""b/" / výstup = """""""""""""""""""""""""""""b/" i = 15 / ind = 31 / strlen(string) = 32 / vstup = """""""""""""""""""""""""""""""" / výstup = """""""""""""""""""""""""""""""" i = 16 / ind = 33 / strlen(string) = 33 / vstup = """"""""""""""""""""""""""""""""" / výstup = """"""""""""""""""""""""""""""""" i = 17 / ind = 35 / strlen(string) = 35 / vstup = """"""""""""""""""""""""""""""""""" / výstup = """"""""""""""""""""""""""""""""""" i = 18 / ind = 37 / strlen(string) = 37 / vstup = """"""""""""""""""""""""""""""""""""" / výstup = """"""""""""""""""""""""""""""""""""" i = 19 / ind = 39 / strlen(string) = 39 / vstup = """"""""""""""""""""""""""""""""""""""" / výstup = """"""""""""""""""""""""""""""""""""""" i = 20 / ind = 41 / strlen(string) = 41 / vstup = """"""""""""""""""""""""""""""""""""""""" / výstup = """"""""""""""""""""""""""""""""""""""""" i = 21 / ind = 43 / strlen(string) = 43 / vstup = """"""""""""""""""""""""""""""""""""""""""" / výstup = """"""""""""""""""""""""""""""""""""""""""" i = 22 / ind = 45 / strlen(string) = 45 / vstup = """"""""""""""""""""""""""""""""""""""""""""" / výstup = """"""""""""""""""""""""""""""""""""""""""""" … i = 2045 / ind = 4091 / strlen(string) = 4091 / vstup = """""""""""""""""""""… i = 2046 / ind = 4093 / strlen(string) = 4093 / vstup = """""""""""""""""""""… i = 2047 / ind = 4095 / strlen(string) = 4095 / vstup = """""""""""""""""""""… i = 2048 / ind = 4097 / strlen(string) = 4097 / vstup = """""""""""""""""""""… i = 2049 / ind = 4099 / strlen(string) = 4099 / vstup = """""""""""""""""""""… i = 2050 / ind = 4130 / strlen(string) = 4102 / vstup = """""""""""""""""""""… i = 2051 / ind = 4132 / strlen(string) = 4102 / vstup = """""""""""""""""""""… i = 2052 / ind = 4134 / strlen(string) = 4102 / vstup = """""""""""""""""""""… i = 2053 / ind = 4136 / strlen(string) = 4102 / vstup = """""""""""""""""""""… i = 2054 / ind = 4138 / strlen(string) = 4102 / vstup = """""""""""""""""""""… … i = 2544 / ind = 5118 / strlen(string) = 4102 / vstup = """""""""""""""""""""… i = 2545 / ind = 5120 / strlen(string) = 4100 / vstup = """""""""""""""""""""… i = 2546 / ind = 5122 / strlen(string) = 4102 / vstup = """""""""""""""""""""… … i = 2672 / ind = 5374 / strlen(string) = 4102 / vstup = """""""""""""""""""""… i = 2673 / ind = 5376 / strlen(string) = 4100 / vstup = """""""""""""""""""""… i = 2674 / ind = 5378 / strlen(string) = 4102 / vstup = """""""""""""""""""""… … i = 2800 / ind = 5630 / strlen(string) = 4102 / vstup = """""""""""""""""""""… i = 2801 / ind = 5632 / strlen(string) = 4100 / vstup = """""""""""""""""""""… i = 2802 / ind = 5634 / strlen(string) = 4102 / vstup = """""""""""""""""""""… … i = 2928 / ind = 5886 / strlen(string) = 4102 / vstup = """""""""""""""""""""… i = 2929 / ind = 5888 / strlen(string) = 4100 / vstup = """""""""""""""""""""… i = 2930 / ind = 5890 / strlen(string) = 4102 / vstup = """""""""""""""""""""… … i = 3056 / ind = 6142 / strlen(string) = 4102 / vstup = """""""""""""""""""""… i = 3057 / ind = 6144 / strlen(string) = 4100 / vstup = """""""""""""""""""""… i = 3058 / ind = 6146 / strlen(string) = 4102 / vstup = """""""""""""""""""""… … i = 3184 / ind = 6398 / strlen(string) = 4102 / vstup = """""""""""""""""""""… i = 3185 / ind = 6400 / strlen(string) = 4100 / vstup = """""""""""""""""""""… i = 3186 / ind = 6402 / strlen(string) = 4102 / vstup = """""""""""""""""""""… … i = 3312 / ind = 6654 / strlen(string) = 4102 / vstup = """""""""""""""""""""… i = 3313 / ind = 6656 / strlen(string) = 4100 / vstup = """""""""""""""""""""… i = 3314 / ind = 6658 / strlen(string) = 4102 / vstup = """""""""""""""""""""… … i = 3440 / ind = 6910 / strlen(string) = 4102 / vstup = """""""""""""""""""""… i = 3441 / ind = 6912 / strlen(string) = 4100 / vstup = """""""""""""""""""""… i = 3442 / ind = 6914 / strlen(string) = 4102 / vstup = """""""""""""""""""""… … i = 3568 / ind = 7166 / strlen(string) = 4102 / vstup = """""""""""""""""""""… i = 3569 / ind = 7168 / strlen(string) = 4100 / vstup = """""""""""""""""""""… i = 3570 / ind = 7170 / strlen(string) = 4102 / vstup = """""""""""""""""""""… … i = 3696 / ind = 7422 / strlen(string) = 4102 / vstup = """""""""""""""""""""… i = 3697 / ind = 7424 / strlen(string) = 4100 / vstup = """""""""""""""""""""… i = 3698 / ind = 7426 / strlen(string) = 4102 / vstup = """""""""""""""""""""… … i = 3792 / ind = 7614 / strlen(string) = 4102 / vstup = """""""""""""""""""""… i = 3793 / ind = 7616 / strlen(string) = 4102 / vstup = """""""""""""""""""""…
Většinou roste po dvou, až se ustálí na hodnotě 4102, ovšem s občasnými anomáliemi 4100.
Když zkrátíme MAX_STRLEN
na 100, roste hodnota na 105, kde se zastaví (a program nestihne spadnout). A za tou hromadou uvozovek je vždy ještě další znak a na každém řádku jiný (že by to byl občas nulový bajt a tím pádem délka 4100 místo 4102? :-).
Jak se může stát, že délka proměnné string
roste, když do string
v průběhu cyklu nic nepřiřazujeme?
Vzpomínáte na ty statické lokální proměnné?
Statická lokální proměnná je něco jako globální proměnná, taky je v programu jenom jednou, akorát je vidět jen z té jedné funkce – a při každém volání té funkce je vidět ta samá.
Věci tedy nejsou, jaké se zdají být, vstup je výstupem, string
je csv
. Teď už chápu, co se mi GDB snažil říct tím <csv> u proměnné string
při druhém průchodu funkcí:
Funkce csv_escape()
tedy funguje správně jen za předpokladu, že nechcete volat něco jako csv_escape(csv_escape(…))
, protože jakmile pozře svůj vlastní výstup, tak se zacyklí.
Možná by stačilo nahradit return csv;
vracením kopie, ale pak je zase otázka, zda by nedocházelo k únikům paměti… pořádnou opravu raději přenechám někomu, kdo umí C líp. A můžeme se vrátit někam doprostřed našeho snažení: je potřeba dostat opravu e049c88
do distribucí.
Za sebe tuhle část zakončím citátem autora knih The Art of Computer Programming a typografického systému TeX, Donnalda Knutha:
The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming.
I když oprava chyby nedopadla úplně podle mých představ a dokonalého řešení jsem dnes nedosáhl, doufám, že i tak vás tento článek povzbudí k hledání a opravování chyb nebo alespoň jejich hlášení. Svobodný software nám to umožňuje – tak ho pojďme dělat (ještě) lepším!
Abyste napsali dobrý (reprodukovatelný) popis chyby nemusíte umět programovat vůbec. A i když neovládáte daný programovací jazyk, stále můžete najít, kde přesně se chyba nachází a v čem spočívá – ostatně to je ten největší kus práce – oprava pak bývá obvykle na pár řádek.
Témata: [svobodný software] [GNU/Linux] [C]
zdravim,
super clanek. Presne jste trefil tema, o kterem se moc nepise, ale presto jej denne resime.
gf
fuuj, tak to je ina prasaren. ciste riesenie samozrejme (v Ccku) je to, ze na vstup dodame vystupny buffer aj s informaciou o jeho velkosti.
To jsem vlastně navrhoval tady:
I had problems with CSV output. I analyzed the source code and did some debugging and then I realized, that the bug #36 was already fixed upstream and it "just" remains in distributions.
But anyway the function
csv_escape()
IMHO should be improved or at least well documented because it has some subtle limitations:
- the input length is checked against
MAX_STRLEN
but during escaping the length grows so it will not fit into thechar csv[MAX_STRLEN+1]
. Of course, current filesystems have also limitations so it might not be an issue. But the limits could be checked inside the loop. And on the other hand, thestrlen(string)
could be done only once (outside the loop).- using static local variables and returning pointers to them from the function might lead (and really leads, as seen in #36) to very unexpected behavior. What about passing the buffer from outside the function as its parameter?
BTW: are you going to release 3.15? (according to the wiki, the last released version is still 3.14 from 2010)
přijde mi to trochu těžkopádné, ale asi to jinak nejde.
BTW: nemáš tip na nějaký dobrý kód v C pro inspiraci? Případně C++ (to je zase trochu jiná kapitola). Ale něco menšího, Jádro asi jen tak nepřečtu :-)
No tak prave kernel by som za priklad na dobry C kod nevydaval. :) Tym, ze tam prispieva plno ludi, tak to aj podla toho vyzera. Zial takto z hlavy si na nejaky projekt nespomeniem, zalezi, aj co od toho cakas, ci sa chces nieco nove naucit (tam mozno pomoze skor dobra kniha) alebo len tak zo zvedavosti. :)
Spíš se něco naučit, odkoukat reálné příklady z praxe. Učebnicové příklady obvykle vysvětlují jednu věc, ale chybí tomu kontext celého programu kolem toho. Něco v podobném rozsahu jako ty inotify-tools
(něco přes 3 000 řádků kódu).
"static char csv[MAX_STRLEN+1];" je globalni promenna viditelna jen v te funkci.
Smysl muze byt optimalizace i usnadneni pouziti, nemusite alokovat/dealokovat novy retezec.
Tento kod neni bezpecny z pohledu vlaken (neni thread-safe), coz tedy asi neni problem.
Take je potreba davat pozor na to, jak se pracuje s vyslednou hodnotou funkce (vraci ukazatel na globalni pole).
"static unsigned int i, ind;" se zda zbytecny, napada me jen usetreni mista na zasobniku (call stack).
Pole v C je vlastne jen "blok pameti", o ktery se musime starat sami.
"nahradit return csv; vracením kopie" by jak pisete zpusobilo uniky pameti (zbytek kodu nepocita s alokovanym retezcem).
Oprava chyb nalezenych v clanku v duchu toho kodu by mohla byt:
Zmena:
// Max array len = all chars need escape + quotes + null terminator static char csv[2*MAX_STRLEN+2+1];
Pridani:
// May be already escaped if (string == csv) { return csv; // already escaped, in string is result of this function }